home *** CD-ROM | disk | FTP | other *** search
/ Skunkware 5 / Skunkware 5.iso / src / Tools / freeWAIS-sf-1.1 / ir / irinv.c < prev    next >
Encoding:
C/C++ Source or Header  |  1994-09-01  |  60.5 KB  |  1,813 lines

  1. /* WIDE AREA INFORMATION SERVER SOFTWARE:
  2.    No guarantees or restrictions.  See the readme file for the full standard
  3.    disclaimer.
  4.  
  5.    Brewster@think.com
  6. */
  7.  
  8. /* Copyright (c) CNIDR (see ../COPYRIGHT) */
  9.  
  10.  
  11. /* Change log:
  12.  * $Log: irinv.c,v $
  13.  * Revision 1.11  1994/09/02  14:34:21  pfeifer
  14.  * fixed overlapping memory copies
  15.  *
  16.  * Revision 1.10  1994/07/13  09:55:23  pfeifer
  17.  * Negative numerics. Syntable freed to early
  18.  *
  19.  * Revision 1.9  1994/07/13  07:52:36  huynh1
  20.  * Uli
  21.  *
  22.  * Revision 1.8  1994/06/03  10:50:01  huynh1
  23.  * bug in (field_)merge_index_file corrected.
  24.  *
  25.  * Revision 1.7  1994/05/26  15:09:57  huynh1
  26.  * no change. beta
  27.  *
  28.  * Revision 1.6  1994/05/20  12:56:18  pfeifer
  29.  * beta
  30.  *
  31.  * Revision 1.5  1994/04/06  23:50:22  pfeifer
  32.  * 08
  33.  *
  34.  * Revision 1.4  1994/03/23  12:58:14  huynh1
  35.  * init_add_word modified.
  36.  * patchlevel 07.
  37.  *
  38.  * Revision 1.3  1994/03/08  20:43:00  huynh1
  39.  * Patchlevel 04
  40.  *
  41.  * Revision 1.2  1994/02/14  10:42:51  huynh1
  42.  * new code for field concept added.
  43.  *
  44.  * Revision 1.3  1993/09/23  03:00:07  pfeifer
  45.  * undid swap.
  46.  * Fixed bug with string compare (strcmp) for ISO-Chars
  47.  *
  48.  * Revision 1.2  1993/09/23  02:12:20  pfeifer
  49.  * swaped from_version and into_version merge_index_file
  50.  *
  51.  * Revision 1.1  1993/02/16  15:05:35  freewais
  52.  * Initial revision
  53.  *
  54.  * Revision 1.28  92/04/28  16:55:50  morris
  55.  * added boolean to serial engine
  56.  * 
  57.  * Revision 1.27  92/03/19  09:34:26  morris
  58.  * fixed the dictionary header to accurately indicate the number of blocks
  59.  * 
  60.  * Revision 1.26  92/03/01  16:11:16  brewster
  61.  * took out the analyze_hashtable_distribution
  62.  * 
  63.  * Revision 1.25  92/02/12  13:27:31  jonathan
  64.  * Added "$Log: irinv.c,v $
  65.  * Revision 1.11  1994/09/02  14:34:21  pfeifer
  66.  * fixed overlapping memory copies
  67.  *
  68.  * Revision 1.10  1994/07/13  09:55:23  pfeifer
  69.  * Negative numerics. Syntable freed to early
  70.  *
  71.  * Revision 1.9  1994/07/13  07:52:36  huynh1
  72.  * Uli
  73.  *
  74.  * Revision 1.8  1994/06/03  10:50:01  huynh1
  75.  * bug in (field_)merge_index_file corrected.
  76.  *
  77.  * Revision 1.7  1994/05/26  15:09:57  huynh1
  78.  * no change. beta
  79.  *
  80.  * Revision 1.6  1994/05/20  12:56:18  pfeifer
  81.  * beta
  82.  *
  83.  * Revision 1.5  1994/04/06  23:50:22  pfeifer
  84.  * 08
  85.  *
  86.  * Revision 1.4  1994/03/23  12:58:14  huynh1
  87.  * init_add_word modified.
  88.  * patchlevel 07.
  89.  *
  90.  * Revision 1.3  1994/03/08  20:43:00  huynh1
  91.  * Patchlevel 04
  92.  *
  93.  * Revision 1.2  1994/02/14  10:42:51  huynh1
  94.  * new code for field concept added.
  95.  *
  96.  * Revision 1.3  1993/09/23  03:00:07  pfeifer
  97.  * undid swap.
  98.  * Fixed bug with string compare (strcmp) for ISO-Chars
  99.  *
  100.  * Revision 1.2  1993/09/23  02:12:20  pfeifer
  101.  * swaped from_version and into_version merge_index_file
  102.  *
  103.  * Revision 1.1  1993/02/16  15:05:35  freewais
  104.  * Initial revision
  105.  *
  106.  * Revision 1.28  92/04/28  16:55:50  morris
  107.  * added boolean to serial engine
  108.  * 
  109.  * Revision 1.27  92/03/19  09:34:26  morris
  110.  * fixed the dictionary header to accurately indicate the number of blocks
  111.  * 
  112.  * Revision 1.26  92/03/01  16:11:16  brewster
  113.  * took out the analyze_hashtable_distribution
  114.  * " so RCS will put the log message in the header
  115.  * 
  116. */
  117.  
  118. /* Inverted file accessor functions. 
  119.    
  120. Main functions:
  121.   finished_add_word
  122.  
  123. */
  124.  
  125.  
  126. /* #include <string.h> /* for memset() */
  127.  
  128. #include "cutil.h"
  129. #include "futil.h"
  130. #include "irhash.h"
  131. #include "hash.h"
  132. #include "panic.h"
  133. #include "irfiles.h"
  134. #include "irext.h"
  135.  
  136. /* ================== */
  137. /* === Index file === */
  138. /* ================== */
  139.  
  140. /*This is an implementation of the inverted file itself.
  141.  * An index_block_number is the position in the file that the
  142.  * entry starts.
  143.  * Index block 0 is the null pointer.
  144.  * The header contains the number of different words in the file.
  145.  *
  146.  */
  147.  
  148. /* the format of an index block is:
  149.  *  in the first byte:
  150.  *  INDEX_BLOCK_FULL_FLAG 
  151.  *    || INDEX_BLOCK_NOT_FULL_FLAG
  152.  *    || INDEX_BLOCK_DICTIONARY_FLAG
  153.  * if index_block_full_flag
  154.  *  next_index_block
  155.  *  index_block_size
  156.  *  stuff
  157.  *  (the number of valid entries is index_block_size - index_block_header_size)
  158.  * if index_block_not_full_flag
  159.  *  number_of_valid_entries
  160.  *  index_block_size
  161.  *  stuff
  162.  * if index_block_dictionary_flag
  163.  *  last_index_block      NEXT_INDEX_BLOCK_SIZE (0 when this is the last)
  164.  *  index_block_size      INDEX_BLOCK_SIZE_SIZE
  165.  *  number_of_occurances  NUMBER_OF_OCCURANCES_SIZE
  166.  *  word (followed by \n)
  167.  *
  168.  *
  169.  * "Stuff" is a list of word occurances of the format:
  170.  * (this is written in irhash.c and read in sersrch.c)
  171.  *     doc_id
  172.  *    character_position
  173.  *    weight
  174.  *    doc_id
  175.  *    character_position
  176.  *     weight 
  177.  *     etc.
  178.  *
  179.  *   It should be (probably in release 9):
  180.  *    doc_id (with high bit set)
  181.  *    weight
  182.  *    character_position
  183.  *    ...
  184.  *    character_position
  185.  *    doc_id
  186.  *    weight
  187.  *    character_position
  188.  *    etc.
  189.  *     
  190.  */
  191.  
  192. long 
  193. write_dictionary_index_block(number_of_occurances,word,stream)
  194. long number_of_occurances;
  195. char *word;
  196. FILE *stream;
  197. /* returns a pointer to the index block allocated */
  198. {
  199.   /* this assumes that the stream is positioned at the end */
  200.   long before_length = ftell(stream); /* file_length(stream); */
  201.   
  202.   long index_block_size = 
  203.     INDEX_BLOCK_FLAG_SIZE +
  204.       NEXT_INDEX_BLOCK_SIZE +
  205.     INDEX_BLOCK_SIZE_SIZE +
  206.       NUMBER_OF_OCCURANCES_SIZE +
  207.         strlen(word) +
  208.           strlen("\n");    /* this is done this way for PC's (necessary?) */
  209.   /* printf("writing dict entry to position %ld\n", before_length); */
  210.   /* grow the file */
  211.   /* in this implementation, the file is always filled by the fwrite,
  212.    * so it will grow itself.
  213.    * grow_file(stream, before_length + how_large);
  214.    * file length leaves the stream at the end, so we are all set.
  215.    * s_fseek(stream, before_length, SEEK_SET);
  216.    */
  217.   write_bytes(INDEX_BLOCK_DICTIONARY_FLAG, INDEX_BLOCK_FLAG_SIZE,
  218.           stream);    
  219.   write_bytes(0L, NEXT_INDEX_BLOCK_SIZE, stream);
  220.   write_bytes(index_block_size, INDEX_BLOCK_SIZE_SIZE, stream);
  221.  
  222.   write_bytes(number_of_occurances, NUMBER_OF_OCCURANCES_SIZE, 
  223.           stream);
  224.   if(strlen(word) > MAX_WORD_LENGTH) /* for debugging */
  225.     waislog(WLOG_HIGH, WLOG_ERROR, 
  226.         "Word: '%s' is too long (%ld characters) for some reason.",
  227.         word, strlen(word));
  228.  
  229.   fprintf(stream, "%s\n", word); 
  230.  
  231.   /* just to check
  232.   { long after_length = ftell(stream);
  233.     if(after_length - before_length != index_block_size){
  234.       waislog(WLOG_HIGH, WLOG_ERROR, 
  235.           "dictionary entry size is %ld, and we thought %ld",
  236.           after_length - before_length, index_block_size);
  237.     }
  238.   }
  239.   */
  240.  
  241.   return(before_length);
  242. }
  243.  
  244. #ifdef everneeded
  245. static long modify_dictionary_index_block 
  246.   _AP((long index_block,long last_index_block,long number_of_occurances,
  247.        FILE* stream));
  248.  
  249. static long modify_dictionary_index_block(index_block,last_index_block,number_of_occurances,stream)
  250. long index_block;   
  251. long last_index_block;
  252. long number_of_occurances;
  253. FILE* stream;
  254.  
  255.      /* this does not allow one to change the word itself, only the entries 
  256.     around it.  It will panic if the index_block is not a valid block.
  257.     This returns the the stream to pointing at the end of the file.
  258.     */
  259. {
  260.   s_fseek(stream, index_block, SEEK_SET);
  261.   if(INDEX_BLOCK_DICTIONARY_FLAG != read_bytes(INDEX_BLOCK_FLAG_SIZE, stream))
  262.     panic("the index block %ld is not a legal dictionary block",
  263.       index_block);
  264.   write_bytes(number_of_occurances, NUMBER_OF_OCCURANCES_SIZE, 
  265.           stream);
  266.   write_bytes(last_index_block, NEXT_INDEX_BLOCK_SIZE, stream);
  267.   read_bytes(INDEX_BLOCK_SIZE_SIZE, stream); /* ignore it */
  268.   write_bytes(number_of_occurances, NUMBER_OF_OCCURANCES_SIZE, 
  269.           stream);
  270.   s_fseek(stream, 0L, SEEK_END);
  271. }
  272.  
  273. #endif /* def everneeded */
  274.  
  275. static long next_dictionary_index_block
  276.   _AP((long* index_block_size,long* number_of_occurances,char* word,
  277.        FILE* stream));
  278.  
  279. static long
  280. next_dictionary_index_block(index_block_size,number_of_occurances,word,stream)
  281. long *index_block_size;
  282. long *number_of_occurances;
  283. char *word;
  284. FILE *stream;
  285. {
  286.   /* this read the dictionary index block from the index stream.
  287.      It assumes the stream is positioned at the start of a dictionary
  288.      block, and will return non-0 if it is not.
  289.      returns 0 if it succeeds.
  290.      returns -1 if it is at the of a file.
  291.      returns -2 if it read something strange.
  292.      Always sets word length to 0 if it fails. */
  293.   long index_block_flag;
  294.   char temp[MAX_WORD_LENGTH + 2];
  295.  
  296.   word[0] = '\0';
  297.  
  298.   index_block_flag = read_bytes(INDEX_BLOCK_FLAG_SIZE, stream);
  299.   if(index_block_flag == EOF){
  300.     /* not an error, it is the way it knows it is done 
  301.     waislog(WLOG_HIGH, WLOG_ERROR, "Hit the end of the inverted file too early");
  302.     */
  303.     return(-1);
  304.   }
  305.   if(index_block_flag != INDEX_BLOCK_DICTIONARY_FLAG){
  306.     waislog(WLOG_HIGH, WLOG_ERROR, 
  307.         "Did not find a dictionary entry, flag is %ld at file position %ld", 
  308.         index_block_flag, ftell(stream) - INDEX_BLOCK_FLAG_SIZE);
  309.     return(-2);
  310.   }
  311.   (void)read_bytes(NEXT_INDEX_BLOCK_SIZE, stream);
  312.   *index_block_size = read_bytes(INDEX_BLOCK_SIZE_SIZE, stream);
  313.   *number_of_occurances = read_bytes(NUMBER_OF_OCCURANCES_SIZE,
  314.                      stream);
  315.   fgets(temp, MAX_WORD_LENGTH + 2, stream); /* 2 is for the \n and '\0' */
  316.  
  317.   /* trim the \n */
  318.   if(temp[strlen(temp) - 1] == '\n'){
  319.     temp[strlen(temp) - 1] = '\0';
  320.   }
  321.   strcpy(word, temp);
  322.   /* printf("Merging word '%s'\n", word); */
  323.   return(0);
  324. }
  325.  
  326. long
  327. read_dictionary_index_block(index_block,
  328.                 last_index_block,
  329.                 index_block_size,
  330.                 number_of_occurances,
  331.                 word,
  332.                 stream)
  333. long index_block;
  334. long *last_index_block;
  335. long *index_block_size;
  336. long *number_of_occurances;
  337. char *word;
  338. FILE *stream;
  339. {
  340.   /* this read the dictionary index block from the index stream.
  341.      returns 0 if it succeeds. */
  342.   long answer;
  343.   s_fseek(stream, index_block, SEEK_SET);
  344.   if(0 > (answer = next_dictionary_index_block(index_block_size,
  345.                             number_of_occurances,
  346.                             word,
  347.                             stream)))
  348.     panic("read dictionary block %ld failed", index_block);
  349.  
  350.   *last_index_block = 0;
  351.   return(answer);
  352. }
  353.  
  354.  
  355. long allocate_index_block(how_large,stream)
  356. long how_large;
  357. FILE* stream;
  358. {
  359.   /* This returns a pointer for an index block.
  360.      It creates the space and writes the header.
  361.      how_large includes the header.
  362.      Returns the block_number (the byte address of first byte in the block */
  363.   /* this assumes that the stream is positioned at the end */
  364.   long before_length = ftell(stream); /* file_length(stream); */
  365.   /* grow the file */
  366.   /* in this implementation, the file is always filled by the fwrite,
  367.    * so it will grow itself.
  368.    * grow_file(stream, before_length + how_large);
  369.    * file length leaves the stream at the end, so we are all set.
  370.    * s_fseek(stream, before_length, SEEK_SET);
  371.    */
  372.   write_bytes(INDEX_BLOCK_FULL_FLAG, /* in this version all are full */
  373.           INDEX_BLOCK_FLAG_SIZE,
  374.           stream);
  375.   write_bytes(0L, NEXT_INDEX_BLOCK_SIZE, stream);
  376.   write_bytes(how_large, INDEX_BLOCK_SIZE_SIZE, stream);
  377.   return(before_length);
  378. }
  379.  
  380. #ifdef testing
  381.  
  382. static void scan_index_blocks _AP((char* filename,boolean verbose));
  383.  
  384. static void scan_index_blocks(filename,verbose)
  385. char *filename;
  386. boolean verbose;
  387. {
  388.   /* this is a debugging routine for checking the inverted index file */
  389.   long current_index_block = INDEX_HEADER_SIZE;
  390.   FILE *stream = s_fopen(filename, "rb");
  391.   long length = file_length(stream);
  392.   
  393.   s_fseek(stream, current_index_block, SEEK_SET);
  394.   printf("File length %ld\n", length);
  395.  
  396.   while(current_index_block < length){
  397.     long flag = read_bytes(INDEX_BLOCK_FLAG_SIZE, stream);
  398.     long next_index_block = read_bytes(NEXT_INDEX_BLOCK_SIZE, stream);
  399.     long index_block_size = read_bytes(INDEX_BLOCK_SIZE_SIZE, stream);
  400.     if(flag == INDEX_BLOCK_DICTIONARY_FLAG){
  401.       long last_index_block;
  402.       long index_block_size;
  403.       long number_of_occurances;
  404.       char word[MAX_WORD_LENGTH + 1];
  405.       if(0 > read_dictionary_index_block(current_index_block,
  406.                   &last_index_block,
  407.                   &index_block_size,
  408.                   &number_of_occurances,
  409.                   word,
  410.                   stream))
  411.     panic("read dictionary index block failed");
  412.       if(verbose)
  413.     printf("%5ld: size %3ld Dictionary entry '%s', number of occurances %ld last block %ld\n",
  414.            current_index_block, index_block_size, word, 
  415.            number_of_occurances, next_index_block);
  416.     }
  417.     else if(flag == INDEX_BLOCK_NOT_FULL_FLAG){
  418.       if(verbose)
  419.     printf("%5ld: size %3ld Not full, valid entries %ld\n",
  420.            current_index_block, index_block_size, next_index_block);
  421.     }
  422.     else if(flag == INDEX_BLOCK_FULL_FLAG){
  423.       if(verbose)
  424.     printf("%5ld: size %3ld full block, next block %ld\n",
  425.          current_index_block, index_block_size, next_index_block);
  426.     }
  427.     else 
  428.       panic("bad entry %ld (ftell %ld), flag was %ld", 
  429.         current_index_block, 
  430.         ftell(stream), flag);
  431.     current_index_block += index_block_size;
  432.     s_fseek(stream, current_index_block, SEEK_SET);
  433.   }
  434.   s_fclose(stream);
  435. }   
  436.  
  437. #endif /* def testing */
  438.  
  439. #define COPY_BLOCK_BUFFER_LENGTH 1000
  440.  
  441. static long copy_stream _AP((FILE* from_stream,FILE* to_stream,long n));
  442.  
  443. static long copy_stream(from_stream,to_stream,n)
  444. FILE *from_stream;
  445. FILE *to_stream;
  446. long n;
  447. {
  448.   char buffer[COPY_BLOCK_BUFFER_LENGTH];
  449.   while(n > 0){
  450.     /* keep reading until we are done */
  451.     long amount_read = fread(buffer, sizeof(char), 
  452.                  MINIMUM(n, COPY_BLOCK_BUFFER_LENGTH),
  453.                  from_stream);
  454.     if(amount_read == 0 || amount_read == EOF)
  455.       return(-1);
  456.     if(amount_read != fwrite(buffer, sizeof(char), amount_read, to_stream))
  457.       return(-1);
  458.     n -= amount_read;
  459.   }
  460.   return(0);
  461. }
  462.  
  463. static void merge_blocks _AP((char* word,FILE* from_stream_1,
  464.                   FILE* from_stream_2,FILE* to_stream));
  465.  
  466. static void merge_blocks(word,from_stream_1,from_stream_2,to_stream)
  467. char* word;
  468. FILE *from_stream_1;
  469. FILE *from_stream_2;
  470. FILE *to_stream;
  471. /* puts from_stream_1 first into to_stream then from_stream_2*/
  472. {
  473.   long flag;
  474.   long index_block_size;
  475.   long other_block_size;
  476.  
  477.   flag = read_bytes(INDEX_BLOCK_FLAG_SIZE, from_stream_1);
  478.   (void)read_bytes(NEXT_INDEX_BLOCK_SIZE, from_stream_1);
  479.   index_block_size = read_bytes(INDEX_BLOCK_SIZE_SIZE, from_stream_1);
  480.  
  481.   if(flag == EOF) return;
  482.   if(flag != INDEX_BLOCK_FULL_FLAG)
  483.     panic("the next index block is not a full block");
  484.  
  485.   flag = read_bytes(INDEX_BLOCK_FLAG_SIZE, from_stream_2);
  486.   (void)read_bytes(NEXT_INDEX_BLOCK_SIZE, from_stream_2);
  487.   other_block_size = read_bytes(INDEX_BLOCK_SIZE_SIZE, from_stream_2);
  488.  
  489.   if(flag != INDEX_BLOCK_FULL_FLAG)
  490.     panic("the next index block is not a full block");
  491.  
  492.   write_bytes(flag, INDEX_BLOCK_FLAG_SIZE, to_stream);
  493.   write_bytes(0L, NEXT_INDEX_BLOCK_SIZE, to_stream);
  494.   if((index_block_size + other_block_size) >
  495.      (1L << (INDEX_BLOCK_SIZE_SIZE * 8))){
  496.     /* then the block is too large to be represented in the 
  497.        index_block_size_size.  This routine takes the radical step to
  498.        eliminate it completely.  The "right" thing to do is to
  499.        chain blocks, but I dont think it is worth it since this should not
  500.        happen very often.  If it does happen often then bump up
  501.        INDEX_BLOCK_SIZE_SIZE. */
  502.     waislog(WLOG_LOW, WLOG_INFO,
  503.         "Can not merge the index block for %s, since it would create one that is too big.  Deleting it.",word);
  504.     write_bytes(INDEX_BLOCK_HEADER_SIZE, INDEX_BLOCK_SIZE_SIZE, to_stream);
  505.     s_fseek(from_stream_1, index_block_size - INDEX_BLOCK_HEADER_SIZE, SEEK_CUR);
  506.     s_fseek(from_stream_2, other_block_size - INDEX_BLOCK_HEADER_SIZE, SEEK_CUR);
  507.   }
  508.   else{ /* copy away */
  509.     write_bytes(index_block_size + other_block_size - INDEX_BLOCK_HEADER_SIZE, 
  510.         INDEX_BLOCK_SIZE_SIZE, to_stream);
  511.     copy_stream(from_stream_1, to_stream, index_block_size - INDEX_BLOCK_HEADER_SIZE);
  512.     copy_stream(from_stream_2, to_stream, other_block_size - INDEX_BLOCK_HEADER_SIZE);
  513.   }
  514. }
  515.  
  516. static void make_dummy_block _AP((FILE* from_stream_1,FILE* from_stream_2,
  517.                   FILE* to_stream));
  518.  
  519. static void make_dummy_block(from_stream_1,from_stream_2,to_stream)
  520. FILE *from_stream_1;
  521. FILE *from_stream_2;
  522. FILE *to_stream;
  523. /* deletes block from_stream_1 and from_stream_2 and makes a 0 length
  524.    block on to_stream */
  525. {
  526.   long flag;
  527.   long index_block_size;
  528.   long other_block_size;
  529.     
  530.   flag = read_bytes(INDEX_BLOCK_FLAG_SIZE, from_stream_1);
  531.   (void)read_bytes(NEXT_INDEX_BLOCK_SIZE, from_stream_1);
  532.   index_block_size = read_bytes(INDEX_BLOCK_SIZE_SIZE, from_stream_1);
  533.  
  534.   if(flag == EOF) return;
  535.   if(flag != INDEX_BLOCK_FULL_FLAG)
  536.     panic("the next index block is not a full block");
  537.  
  538.   flag = read_bytes(INDEX_BLOCK_FLAG_SIZE, from_stream_2);
  539.   (void)read_bytes(NEXT_INDEX_BLOCK_SIZE, from_stream_2);
  540.   other_block_size = read_bytes(INDEX_BLOCK_SIZE_SIZE, from_stream_2);
  541.   if(flag != INDEX_BLOCK_FULL_FLAG)
  542.     panic("the next index block is not a full block");
  543.  
  544.   write_bytes(flag, INDEX_BLOCK_FLAG_SIZE, to_stream);
  545.   write_bytes(0L, NEXT_INDEX_BLOCK_SIZE, to_stream);
  546.   write_bytes(INDEX_BLOCK_HEADER_SIZE, INDEX_BLOCK_SIZE_SIZE, to_stream);
  547.   s_fseek(from_stream_1, index_block_size - INDEX_BLOCK_HEADER_SIZE, SEEK_CUR);
  548.   s_fseek(from_stream_2, other_block_size - INDEX_BLOCK_HEADER_SIZE, SEEK_CUR);
  549. }
  550.  
  551. static void copy_block _AP((FILE* from_stream,FILE* to_stream));
  552.  
  553. static void copy_block(from_stream,to_stream)
  554. FILE *from_stream;
  555. FILE *to_stream;
  556. /* copies an index block from one stream to another */
  557. { /* copies an index block from from_stream to to_stream */
  558.   long flag;
  559.   long next_index_block;
  560.   long index_block_size;
  561.  
  562.   flag = read_bytes(INDEX_BLOCK_FLAG_SIZE, from_stream);
  563.   next_index_block = read_bytes(NEXT_INDEX_BLOCK_SIZE, from_stream);
  564.   index_block_size = read_bytes(INDEX_BLOCK_SIZE_SIZE, from_stream);
  565.  
  566.   if(flag == EOF) return;
  567.   write_bytes(flag, INDEX_BLOCK_FLAG_SIZE, to_stream);
  568.   write_bytes(next_index_block, NEXT_INDEX_BLOCK_SIZE, to_stream);
  569.   write_bytes(index_block_size, INDEX_BLOCK_SIZE_SIZE, to_stream);
  570.   /* copy the real stuff */
  571.   copy_stream(from_stream, to_stream, 
  572.           index_block_size - INDEX_BLOCK_HEADER_SIZE);
  573. }
  574.  
  575. /* ================== */
  576. /* === Merge File === */
  577. /* ================== */
  578.  
  579. static boolean merge_index_file _AP((long from_version, long into_version,
  580.                      database* db,
  581.                      boolean generate_dictionary));
  582.  
  583. /* Merges a index file (from_version) into another (into_version).
  584.    Returns true if successful */
  585.  
  586. static boolean merge_index_file(into_version, from_version, 
  587.                 db,generate_dictionary)
  588. long into_version;
  589. long from_version;
  590. database* db;
  591. boolean generate_dictionary;
  592. /* 
  593.    This is done by merging both into version -1 and then, 
  594.    renames it to into_version, then deletes from_version. */
  595. {
  596.   char input_filename_1[MAX_FILENAME_LEN]; /* into_version file */
  597.   char input_filename_2[MAX_FILENAME_LEN]; /* from_version file */
  598.   char output_filename[MAX_FILENAME_LEN];  /* version -1 */
  599.  
  600.   FILE *input_stream_1;
  601.   FILE *input_stream_2;
  602.   FILE *output_stream;
  603.   char input_word_1[MAX_WORD_LENGTH + 1];
  604.   char input_word_2[MAX_WORD_LENGTH + 1];
  605.  
  606.   if(generate_dictionary) {
  607.     if(into_version == -2)
  608.       waislog(WLOG_MEDIUM, WLOG_INDEX,
  609.           "Merging version %ld into master version and generating dictionary.",
  610.           from_version);
  611.     else
  612.       waislog(WLOG_MEDIUM, WLOG_INDEX,
  613.           "Merging version %ld into %ld and generating dictionary.",
  614.           from_version, into_version);
  615.   }
  616.   else {
  617.     waislog(WLOG_MEDIUM, WLOG_INDEX, "Merging version %ld and %ld", 
  618.         into_version, from_version);
  619.   }
  620.  
  621.   index_filename_with_version(into_version, input_filename_1, db);
  622.   index_filename_with_version(from_version, input_filename_2, db); 
  623.   index_filename_with_version(-1L, output_filename, db); 
  624.   
  625.  
  626.   if(NULL == (input_stream_1 = s_fopen(input_filename_1, "rb")))
  627.     return(false);
  628.  
  629.   if(NULL == (input_stream_2 = s_fopen(input_filename_2, "rb")))
  630.     return(false);
  631.  
  632.   if(NULL == (output_stream = s_fopen(output_filename, "wb")))
  633.     return(false);
  634.  
  635.   {
  636.     long index_block_size_1;
  637.     long number_of_occurances_1;
  638.     long number_of_words_1;
  639.     long index_block_size_2;
  640.     long number_of_occurances_2;
  641.     long number_of_words_2;
  642.  
  643.     /* leave room for the number_of_words in the output */
  644.     s_fseek(output_stream, INDEX_HEADER_SIZE, SEEK_SET);
  645.  
  646.     /* read the number of words in the 2 input files, this is the maximum 
  647.        number of words that can be in the dictionary,  It is used by 
  648.        init_dict_file to set asside space for the dictionary_header_block.
  649.        It is very likley that some of the words are duplicates, so the actual
  650.        size of the header_block will be smaller.  But we never know until we
  651.        do the merge, so we have to give enough space just in case.
  652.        NOTE - in the case where generate_dictioary is false, we still need
  653.        to do this because we need to skip the header_size in each of the
  654.        input streams 
  655.      */
  656.     number_of_words_1 = read_bytes(INDEX_HEADER_SIZE,input_stream_1);
  657.     number_of_words_2 = read_bytes(INDEX_HEADER_SIZE,input_stream_2);
  658.     if((number_of_words_1 > 0) && (number_of_words_2 < 0)) 
  659.       db->number_of_words = number_of_words_1 + 1;
  660.     else if((number_of_words_1 < 0) && (number_of_words_2 > 0))
  661.       db->number_of_words = number_of_words_2 + 1;
  662.     else if((number_of_words_1 > 0) && (number_of_words_2 > 0))
  663.       db->number_of_words = number_of_words_1 + number_of_words_2 + 1;
  664.  
  665.     if(db->number_of_words == 0) {
  666.       if((number_of_words_1 == 1 && number_of_words_2 < 1) ||
  667.          (number_of_words_1 < 1 && number_of_words_2 == 1))
  668.         db->number_of_words = 1;
  669.     }
  670.      /* db->number_of_words = read_bytes(INDEX_HEADER_SIZE,input_stream_1) +
  671.       *                       read_bytes(INDEX_HEADER_SIZE,input_stream_2);
  672.       */
  673.  
  674.     if (generate_dictionary) { /* allocate the header block if necessary */
  675.       init_dict_file_for_writing(db);
  676.     }
  677.  
  678.     /* now that the dictioanry_header_block is allocated, we can start counting
  679.        for real */
  680.     db->number_of_words = 0; 
  681.  
  682.     if(-1 > next_dictionary_index_block(&index_block_size_1,
  683.                     &number_of_occurances_1,
  684.                     input_word_1,
  685.                     input_stream_1))
  686.       panic("Read of dictionary block failed in file %s",
  687.         input_filename_1);
  688.     if(-1 > next_dictionary_index_block(&index_block_size_2,
  689.                     &number_of_occurances_2,
  690.                     input_word_2,
  691.                     input_stream_2))
  692.       panic("Read of dictionary block failed in file %s",
  693.         input_filename_2);
  694.  
  695.     while(true){
  696.       long strlen_1 = strlen(input_word_1);
  697.       long strlen_2 = strlen(input_word_2);
  698.       long compare = strcmp(input_word_1, input_word_2); /* +1 if 1 is bigger */
  699.       if (!strlen_1) {          /* Make it work for iso chars */
  700.         if (!strlen_2) compare = 0;
  701.         else           compare = -1;
  702.       }
  703.       if((0 == strlen_1) && 
  704.      (0 == strlen_2)){
  705.     /* printf("Done with merge\n"); */
  706.     /* then we are done. */
  707.     break;
  708.       }
  709.       else if((0 != strlen_1) && (0 != strlen_2) && (0 == compare)){
  710.     /* they are equal */
  711.     /* printf("Merging word %s and %s\n", input_word_1, input_word_2); */
  712.     write_dictionary_index_block(number_of_occurances_1 +
  713.                      number_of_occurances_2, 
  714.                      input_word_1, 
  715.                      output_stream);
  716.     if(generate_dictionary){
  717.       add_word_to_dictionary(input_word_1, ftell(output_stream), 
  718.                  number_of_occurances_1 +
  719.                  number_of_occurances_2, 
  720.                  db);
  721.     }
  722.     db->number_of_words++;
  723.     /* copy file 1 first.  this assumes that file 1 was indexed before
  724.        file 2 */
  725.     if((number_of_occurances_1+number_of_occurances_2) > MAX_OCCURANCES){
  726.       /* too many already, just make a dummy (0 length) */
  727.       if((number_of_occurances_1 < MAX_OCCURANCES) &&
  728.          (number_of_occurances_2 < MAX_OCCURANCES)) {
  729.         /* only print the first time */
  730.         waislog(WLOG_MEDIUM, WLOG_INDEX,
  731.             "Deleting word '%s' since it has %ld occurences (limit %ld).",
  732.             input_word_1,number_of_occurances_1+number_of_occurances_2,
  733.             MAX_OCCURANCES);
  734.       }
  735.       make_dummy_block(input_stream_1, input_stream_2, output_stream);
  736.     }
  737.     else{
  738.       merge_blocks(input_word_1,input_stream_1,input_stream_2,
  739.                output_stream); 
  740.     }
  741.     if(-1 > next_dictionary_index_block(&index_block_size_1,
  742.                         &number_of_occurances_1,
  743.                         input_word_1,
  744.                         input_stream_1))
  745.       panic("Read of dictionary block failed in file %s",
  746.         input_filename_1);
  747.  
  748.     if(-1 > next_dictionary_index_block(&index_block_size_2,
  749.                         &number_of_occurances_2,
  750.                         input_word_2,
  751.                         input_stream_2))
  752.       panic("Read of dictionary block failed in file %s",
  753.         input_filename_2);
  754.       }
  755.       else if(((0 == strlen_1) && (0 != strlen_2) && (0 > compare)) ||
  756.           ((0 != strlen_1) && (0 != strlen_2) && (0 < compare))){
  757.     /* write from block 2 */
  758.     /* printf("From block 2: writing word '%s' not '%s'\n", 
  759.        input_word_2, input_word_1);  */
  760.     write_dictionary_index_block(number_of_occurances_2, input_word_2,
  761.                      output_stream);
  762.     if(generate_dictionary){
  763.       add_word_to_dictionary(input_word_2, ftell(output_stream), 
  764.                  number_of_occurances_2, db);
  765.     }
  766.     db->number_of_words++;
  767.     copy_block(input_stream_2, output_stream);
  768.     if(-1 > next_dictionary_index_block(&index_block_size_2,
  769.                         &number_of_occurances_2,
  770.                         input_word_2,
  771.                         input_stream_2))
  772.       panic("Read of dictionary block failed in file %s",
  773.         input_filename_2);
  774.       }
  775.       else{
  776.     /* write from block 1 */
  777.     /* printf("From block 1: writing word '%s' not '%s'\n", 
  778.        input_word_1, input_word_2); */
  779.     write_dictionary_index_block(number_of_occurances_1, input_word_1,
  780.                      output_stream);
  781.     if(generate_dictionary){
  782.       add_word_to_dictionary(input_word_1, ftell(output_stream), 
  783.                  number_of_occurances_1, db);
  784.     }
  785.     db->number_of_words++;
  786.     copy_block(input_stream_1, output_stream);
  787.     if(-1 > next_dictionary_index_block(&index_block_size_1,
  788.                         &number_of_occurances_1,
  789.                         input_word_1,
  790.                         input_stream_1))
  791.       panic("Read of dictionary block failed in file %s",
  792.         input_filename_1);
  793.       }
  794.     }
  795.   }
  796.  
  797.   /* write out the number of words */
  798.   s_fseek(output_stream, 0L, SEEK_SET);
  799.   write_bytes(db->number_of_words, INDEX_HEADER_SIZE, output_stream);
  800.  
  801.   s_fclose(input_stream_1);
  802.   s_fclose(input_stream_2);
  803.   s_fclose(output_stream);
  804.   /* check resulting file */    
  805.   /* check_index_file(output_filename); */
  806.   /* scan_index_blocks(output_filename); */
  807.  
  808.   /* delete the input files */
  809.   remove(input_filename_1);
  810.   remove(input_filename_2);
  811.   /* These next two calls result in the renaming of the new files ontop of 
  812.      the old
  813.      files. This is the only critical time in which if an online update is 
  814.      happening (the index files are being changed while the 
  815.      queries are being answered).  If a query comes along in another
  816.      process and opens the .inv file after the rename is done, but before
  817.      the finished_add_word_to_dictionary has the change to rename the .dct
  818.      file and therefore opens the old version of the .dct file, then the
  819.      files will be out of synch. */
  820.   rename(output_filename, input_filename_1);  
  821.   if(generate_dictionary)
  822.     finished_add_word_to_dictionary(db);
  823.     
  824. #ifdef THINK_C
  825.   /* set the mac file type to INDX */
  826.   setFileType(input_filename_1, WAIS_INDEX_FILE_TYPE, CREATOR);
  827. #endif /* THINK_C */
  828.  
  829.   return(true);
  830. }
  831.  
  832. #ifdef FIELDS /* tung, 12/93 */
  833. static boolean field_merge_index_file _AP((long from_version, 
  834.                                            long into_version,
  835.                                            database* db,
  836.                                            boolean generate_dictionary,
  837.                                            long field_id));
  838.  
  839. /* Merges a index file (from_version) into another (into_version).
  840.    Returns true if successful */
  841.  
  842. static boolean field_merge_index_file(into_version, from_version, 
  843.                                       db,generate_dictionary, field_id)
  844. long into_version;
  845. long from_version;
  846. database* db;
  847. boolean generate_dictionary;
  848. long field_id;
  849. /* 
  850.    This is done by merging both into version -1 and then, 
  851.    renames it to into_version, then deletes from_version. */
  852. {
  853.   char input_filename_1[MAX_FILENAME_LEN]; /* into_version file */
  854.   char input_filename_2[MAX_FILENAME_LEN]; /* from_version file */
  855.   char output_filename[MAX_FILENAME_LEN];  /* version -1 */
  856.  
  857.   FILE *input_stream_1;
  858.   FILE *input_stream_2;
  859.   FILE *output_stream;
  860.   char input_word_1[MAX_WORD_LENGTH + 1];
  861.   char input_word_2[MAX_WORD_LENGTH + 1];
  862.  
  863.   if(generate_dictionary) {
  864.     if(into_version == -2)
  865.       waislog(WLOG_MEDIUM, WLOG_INDEX,
  866.           "Merging version %ld into master version and generating dictionary of field %s.",
  867.           from_version, db->fields[field_id].field_name);
  868.     else
  869.       waislog(WLOG_MEDIUM, WLOG_INDEX,
  870.           "Merging version %ld into %ld and generating dictionary of field %s.",
  871.           from_version, into_version, db->fields[field_id].field_name);
  872.   }
  873.   else {
  874.     waislog(WLOG_MEDIUM, WLOG_INDEX, "Merging version %ld and %ld", 
  875.         into_version, from_version);
  876.   }
  877.  
  878.   field_index_filename_with_version(into_version, input_filename_1, 
  879.                                     field_id, db);
  880.   field_index_filename_with_version(from_version, input_filename_2, 
  881.                                     field_id, db); 
  882.   field_index_filename_with_version(-1L, output_filename, field_id, db); 
  883.   
  884.  
  885.   if(NULL == (input_stream_1 = s_fopen(input_filename_1, "rb")))
  886.     return(false);
  887.  
  888.   if(NULL == (input_stream_2 = s_fopen(input_filename_2, "rb")))
  889.     return(false);
  890.  
  891.   if(NULL == (output_stream = s_fopen(output_filename, "wb")))
  892.     return(false);
  893.  
  894.   {
  895.     long index_block_size_1;
  896.     long number_of_occurances_1;
  897.     long number_of_words_1;
  898.     long index_block_size_2;
  899.     long number_of_occurances_2;
  900.     long number_of_words_2;
  901.     
  902.     /* leave room for the number_of_words in the output */
  903.     s_fseek(output_stream, INDEX_HEADER_SIZE, SEEK_SET);
  904.  
  905.     /* read the number of words in the 2 input files, this is the maximum 
  906.        number of words that can be in the dictionary,  It is used by 
  907.        init_dict_file to set asside space for the dictionary_header_block.
  908.        It is very likley that some of the words are duplicates, so the actual
  909.        size of the header_block will be smaller.  But we never know until we
  910.        do the merge, so we have to give enough space just in case.
  911.        NOTE - in the case where generate_dictioary is false, we still need
  912.        to do this because we need to skip the header_size in each of the
  913.        input streams 
  914.      */
  915.     
  916.     number_of_words_1 = read_bytes(INDEX_HEADER_SIZE,input_stream_1);
  917.     number_of_words_2 = read_bytes(INDEX_HEADER_SIZE,input_stream_2);
  918.     if((number_of_words_1 > 0) && (number_of_words_2 < 0)) 
  919.       db->fields[field_id].number_of_words = number_of_words_1 + 1;
  920.     else if((number_of_words_1 < 0) && (number_of_words_2 > 0))
  921.       db->fields[field_id].number_of_words = number_of_words_2 + 1;
  922.     else if((number_of_words_1 > 0) && (number_of_words_2 > 0))
  923.       db->fields[field_id].number_of_words = number_of_words_1 + number_of_words_2 + 1;
  924.     else db->fields[field_id].number_of_words = 0;
  925.  
  926.     if(db->fields[field_id].number_of_words == 0) {
  927.       if((number_of_words_1 == 1 && number_of_words_2 < 1) ||
  928.          (number_of_words_1 < 1 && number_of_words_2 == 1))
  929.         db->fields[field_id].number_of_words = 1;
  930.     }
  931.     
  932.     if (generate_dictionary) { /* allocate the header block if necessary */
  933.       field_init_dict_file_for_writing(field_id, db);
  934.     }
  935.     
  936.     /* now that the dictioanry_header_block is allocated, we can start counting
  937.        for real */
  938.     db->fields[field_id].number_of_words = 0; 
  939.     
  940.     if(-1 > next_dictionary_index_block(&index_block_size_1,
  941.                        &number_of_occurances_1,
  942.                        input_word_1,
  943.                        input_stream_1))
  944.       panic("Read of dictionary block failed in file %s",
  945.         input_filename_1);
  946.     if(-1 > next_dictionary_index_block(&index_block_size_2,
  947.                 &number_of_occurances_2,
  948.                 input_word_2,
  949.                 input_stream_2))
  950.       panic("Read of dictionary block failed in file %s",
  951.         input_filename_2);
  952.     while(true){
  953.       long strlen_1 = strlen(input_word_1);
  954.       long strlen_2 = strlen(input_word_2);
  955.       long compare = strcmp(input_word_1, input_word_2); /* +1 if 1 is bigger */
  956.       if (!strlen_1) {          /* Make it work for iso chars */
  957.         if (!strlen_2) compare = 0;
  958.         else           compare = -1;
  959.       }
  960.       if((0 == strlen_1) && 
  961.      (0 == strlen_2)){
  962.     /* printf("Done with merge\n"); */
  963.     /* then we are done. */
  964.     break;
  965.       }
  966.       else if((0 != strlen_1) && (0 != strlen_2) && (0 == compare)){
  967.     /* they are equal */
  968.     /* printf("Merging word %s and %s\n", input_word_1, input_word_2); */
  969.     write_dictionary_index_block(number_of_occurances_1 +
  970.                      number_of_occurances_2, 
  971.                      input_word_1, 
  972.                      output_stream);
  973.     if(generate_dictionary){
  974.       field_add_word_to_dictionary(input_word_1, ftell(output_stream), 
  975.                                        number_of_occurances_1 +
  976.                                        number_of_occurances_2, 
  977.                                        field_id, db);
  978.     }
  979.     db->fields[field_id].number_of_words++;
  980.     /* copy file 1 first.  this assumes that file 1 was indexed before
  981.        file 2 */
  982.     if((number_of_occurances_1+number_of_occurances_2) > MAX_OCCURANCES){
  983.       /* too many already, just make a dummy (0 length) */
  984.       if((number_of_occurances_1 < MAX_OCCURANCES) &&
  985.          (number_of_occurances_2 < MAX_OCCURANCES)) {
  986.         /* only print the first time */
  987.         waislog(WLOG_MEDIUM, WLOG_INDEX,
  988.             "Deleting word '%s' since it has %ld occurences (limit %ld).",
  989.             input_word_1,number_of_occurances_1+number_of_occurances_2,
  990.             MAX_OCCURANCES);
  991.       }
  992.       make_dummy_block(input_stream_1, input_stream_2, output_stream);
  993.     }
  994.     else{
  995.       merge_blocks(input_word_1,input_stream_1,input_stream_2,
  996.                output_stream); 
  997.     }
  998.     if(-1 > next_dictionary_index_block(&index_block_size_1,
  999.                     &number_of_occurances_1,
  1000.                     input_word_1,
  1001.                     input_stream_1))
  1002.       panic("Read of dictionary block failed in file %s",
  1003.         input_filename_1);
  1004.  
  1005.     if(-1 > next_dictionary_index_block(&index_block_size_2,
  1006.                     &number_of_occurances_2,
  1007.                     input_word_2,
  1008.                     input_stream_2))
  1009.       panic("Read of dictionary block failed in file %s",
  1010.         input_filename_2);
  1011.       }
  1012.       else if(((0 == strlen_1) && (0 != strlen_2) && (0 > compare)) ||
  1013.           ((0 != strlen_1) && (0 != strlen_2) && (0 < compare))){
  1014.     /* write from block 2 */
  1015.     /* printf("From block 2: writing word '%s' not '%s'\n", 
  1016.        input_word_2, input_word_1);  */
  1017.     write_dictionary_index_block(number_of_occurances_2, input_word_2,
  1018.                      output_stream);
  1019.     if(generate_dictionary){
  1020.       field_add_word_to_dictionary(input_word_2, ftell(output_stream), 
  1021.                                        number_of_occurances_2, field_id, db);
  1022.     }
  1023.     db->fields[field_id].number_of_words++;
  1024.     copy_block(input_stream_2, output_stream);
  1025.     if(-1 > next_dictionary_index_block(&index_block_size_2,
  1026.                     &number_of_occurances_2,
  1027.                     input_word_2,
  1028.                     input_stream_2))
  1029.       panic("Read of dictionary block failed in file %s",
  1030.         input_filename_2);
  1031.       }
  1032.       else{
  1033.     /* write from block 1 */
  1034.     /* printf("From block 1: writing word '%s' not '%s'\n", 
  1035.        input_word_1, input_word_2); */
  1036.     write_dictionary_index_block(number_of_occurances_1, input_word_1,
  1037.                      output_stream);
  1038.     if(generate_dictionary){
  1039.       field_add_word_to_dictionary(input_word_1, ftell(output_stream), 
  1040.                                        number_of_occurances_1, field_id, db);
  1041.     }
  1042.     db->fields[field_id].number_of_words++;
  1043.     copy_block(input_stream_1, output_stream);
  1044.     if(-1 > next_dictionary_index_block(&index_block_size_1,
  1045.                         &number_of_occurances_1,
  1046.                         input_word_1,
  1047.                         input_stream_1))
  1048.       panic("Read of dictionary block failed in file %s",
  1049.         input_filename_1);
  1050.       }
  1051.     }
  1052.   }
  1053.  
  1054.   /* write out the number of words */
  1055.   s_fseek(output_stream, 0L, SEEK_SET);
  1056.   write_bytes(db->fields[field_id].number_of_words, 
  1057.               INDEX_HEADER_SIZE, output_stream);
  1058.  
  1059.   s_fclose(input_stream_1);
  1060.   s_fclose(input_stream_2);
  1061.   s_fclose(output_stream);
  1062.   /* check resulting file */    
  1063.   /* check_index_file(output_filename); */
  1064.   /* scan_index_blocks(output_filename); */
  1065.  
  1066.   /* delete the input files */
  1067.   remove(input_filename_1);
  1068.   remove(input_filename_2);
  1069.   /* These next two calls result in the renaming of the new files ontop of 
  1070.      the old
  1071.      files. This is the only critical time in which if an online update is 
  1072.      happening (the index files are being changed while the 
  1073.      queries are being answered).  If a query comes along in another
  1074.      process and opens the .inv file after the rename is done, but before
  1075.      the finished_add_word_to_dictionary has the change to rename the .dct
  1076.      file and therefore opens the old version of the .dct file, then the
  1077.      files will be out of synch. */
  1078.   rename(output_filename, input_filename_1);  
  1079.   if(generate_dictionary)
  1080.     field_finished_add_word_to_dictionary(field_id, db);
  1081.     
  1082. #ifdef THINK_C
  1083.   /* set the mac file type to INDX */
  1084.   setFileType(input_filename_1, WAIS_INDEX_FILE_TYPE, CREATOR);
  1085. #endif /* THINK_C */
  1086.  
  1087.   return(true);
  1088. }
  1089. #endif  /* FIELDS */
  1090.  
  1091. /* only works on positive, non-zero numbers, and is slow to boot, yippie */
  1092. static long logcount _AP((long number));
  1093. static long logcount(number)
  1094. long number;
  1095. {
  1096.   long answer = 0;
  1097.   for( ; number > 0; number = number >> 1)
  1098.     answer++;
  1099.   return(answer);
  1100. }
  1101.  
  1102. /*
  1103.  * Originally, the index files produced while building an index were
  1104.  * merged together using a normal binary merge sort.  For example, if
  1105.  * there were 8 files (0 through 7) to be merged, they were merged
  1106.  * in the following order, after all the indexing was finished:
  1107.  *
  1108.  * 0+1 -> 0  2+3 -> 2  4+5 -> 4  6+7 -> 6
  1109.  * 0+2 -> 0  4+6 -> 4
  1110.  * 0+4 -> 0
  1111.  *
  1112.  * The only problem with this is that it means that all of the index
  1113.  * files have to stay around until the entire database has been
  1114.  * indexed.  This means that the amount of disk space needs
  1115.  * temporarily during the indexing grows linearly with the amount of
  1116.  * data being indexed.
  1117.  *
  1118.  * The idea of this routine is to reduce the amount of temporary disk
  1119.  * space used by doing whatever merging it can, DURING the indexing
  1120.  * rather than after it.  The routine gets called whenever an index
  1121.  * file has just been flushed, and it figures out what merging needs
  1122.  * to be performed and does it.
  1123.  *
  1124.  * The merging done by this routine ends up being the same as the
  1125.  * merging done by the old routine at the end of indexing.  However,
  1126.  * it happens in a different order.  For example, once again, if the
  1127.  * total number of index files were 8, this is what would happen (a
  1128.  * number by itself means that it was flushed; number+number->number
  1129.  * refers to a merge):
  1130.  * 
  1131.  * 0 1 0+1->0
  1132.  * 2 3 2+3->2 0+2->0
  1133.  * 4 5 4+5->4
  1134.  * 6 7 6+7->6 4+6->4 0+4->0
  1135.  *
  1136.  * You should be able to convince yourself relatively quickly that the
  1137.  * result of this is that the amount of space needed temporarily
  1138.  * during indexing grows logarithmically with the amount of data being
  1139.  * indexed, rather than linearly.  What's more, no extra time is required.
  1140.  *
  1141.  * There is one complication.  When the total number of files being
  1142.  * indexed is an even power of two, the pattern of merging is regular,
  1143.  * as demonstrated above.  However, if the total number of files is
  1144.  * not an even power of two, things are a bit complicated.  For
  1145.  * example, if there were 11 files instead of 8, we'd have to add this
  1146.  * to the end of the list given above:
  1147.  *
  1148.  * 8 9 8+9->8
  1149.  * 10 8+10->8 0+8->0
  1150.  * 
  1151.  * The algorithm for determining what merges to perform after every
  1152.  * second index file, when the index file just flushed is not the last
  1153.  * one, is simple.  However, the algorithm for determining what to do
  1154.  * on the last file flushed, whether or not it's a power of two, is a
  1155.  * bit more complicated.
  1156.  *
  1157.  * The first algorithm:
  1158.  *
  1159.  * splitter := the number of the file just indexed (starting from 1)
  1160.  * if splitter is odd
  1161.  *   then return
  1162.  * low = splitter - 2  // counting index files from 0
  1163.  * high = splitter - 1 // ditto
  1164.  * twotothe = 1
  1165.  * while splitter is even do
  1166.  *   merge files low and high into low
  1167.  *   high := high - twotothe
  1168.  *   twotothe := twotothe * 2
  1169.  *   low := low - twotothe
  1170.  *   splitter := splitter / 2
  1171.  * done
  1172.  *
  1173.  * The translation from this into the code below should be somewhat
  1174.  * obvious.  What is NOT obvious, however, is all the stuff with
  1175.  * "target", "realhigh" and "where_to_stop", and with the extra loop
  1176.  * wrapped around the code above.  The point of all that is to handle
  1177.  * the second case mentioned above, when we're done indexing and need
  1178.  * to merge everything together.  It works by pretending doing a
  1179.  * series of merges of the first type described above, from the actual
  1180.  * last index file until the next power of two higher than it.  At
  1181.  * each pretended series, only those merges for which the low and high
  1182.  * index files actually still exist are performed.
  1183.  *
  1184.  * In the degenerate case of the second algorithm, when the last index
  1185.  * file being flushed is an even power of two, the outer loop only
  1186.  * runs once and the behavior is the same as in the first algorithm
  1187.  * described above.
  1188.  *
  1189.  * I hope that explains things adequately :-).
  1190.  */
  1191.  
  1192. #ifndef END_MERGE
  1193.  
  1194. static void do_intermediate_merging _AP((database *db, boolean completely));
  1195.  
  1196. static void do_intermediate_merging(db, completely)
  1197. database *db;
  1198. boolean completely;
  1199. {
  1200.   int last = db->index_file_number - 1;
  1201.   int twotothe, low, high, where_to_stop, target, realhigh = last, splitter;
  1202.   char filename[MAX_FILENAME_LEN];
  1203.   char filename2[MAX_FILENAME_LEN];
  1204.  
  1205.   if (last % 2 == 0) {
  1206.     if (! completely) {
  1207.       return;
  1208.     }
  1209.     else {
  1210.       for (where_to_stop = 1; where_to_stop < last + 1;
  1211.        where_to_stop *= 2) /* empty */;
  1212.     }
  1213.   }
  1214.   else {
  1215.     if (completely) {
  1216.       for (where_to_stop = 1; where_to_stop < last + 1;
  1217.        where_to_stop *= 2) /* empty */;
  1218.     }
  1219.     else {
  1220.       where_to_stop = last + 1;
  1221.     }
  1222.   }
  1223.  
  1224.   for (target = ((last % 2) == 0) ? last + 2 : last + 1;
  1225.        target <= where_to_stop; target += 2) {
  1226.  
  1227.     low = target - 2;
  1228.     high = target - 1;
  1229.     twotothe = 1;
  1230.     splitter = target;
  1231.       
  1232.     while (((splitter % 2) == 0) && (low >= 0)) {
  1233.       if (low < realhigh) {
  1234.     if (! merge_index_file(low, (high > realhigh) ? realhigh : high, db,
  1235.                    completely && (! low) &&
  1236.                    !probe_file(index_filename(filename, db)))) {
  1237.       panic("Error in merging file %ld into %ld", low,
  1238.         (high > last) ? last : high);
  1239.     }
  1240.     realhigh = low;
  1241.       }
  1242.       high -= twotothe;
  1243.       twotothe *= 2;
  1244.       low -= twotothe;
  1245.       splitter /= 2;
  1246.     }
  1247.   }
  1248.  
  1249.   if (completely) {
  1250.     if (probe_file(index_filename(filename, db))) {
  1251.       merge_index_file(-2L, 0L, db, true);
  1252.     }
  1253.     else if (last == 0) {
  1254.       touch_file(index_filename_with_version(1, filename, db));
  1255.       merge_index_file(0L, 1, db, true);
  1256.       /* rename 0 into the final name */
  1257.       rename(index_filename_with_version(0L, filename2, db),
  1258.          index_filename(filename, db));
  1259.     }
  1260.   }
  1261. }
  1262. #endif  /* #ifndef END_MERGE */ 
  1263.  
  1264. #ifndef END_MERGE 
  1265. #ifdef FIELDS /* tung, 1/94 */
  1266. static void field_do_intermediate_merging _AP((database *db, boolean completely, long field_id));
  1267.  
  1268. static void field_do_intermediate_merging(db, completely, field_id)
  1269.      database *db;
  1270.      boolean completely;
  1271.      long field_id;
  1272. {
  1273.   long i = field_id;
  1274.   int last = db->fields[i].index_file_number - 1;
  1275.   int twotothe, low, high, where_to_stop, target, realhigh = last, splitter;
  1276.   char filename[MAX_FILENAME_LEN];
  1277.   char filename2[MAX_FILENAME_LEN];
  1278.  
  1279.   twotothe = low = high = where_to_stop = target = splitter = 0;
  1280.   
  1281.   if (last % 2 == 0) {
  1282.     if (! completely) {
  1283.         return;
  1284.       }
  1285.     else {
  1286.       for (where_to_stop = 1; where_to_stop < last + 1;
  1287.            where_to_stop *= 2) /* empty */;
  1288.       }
  1289.   }
  1290.   else {
  1291.     if (completely) {
  1292.       for (where_to_stop = 1; where_to_stop < last + 1;
  1293.            where_to_stop *= 2) /* empty */;
  1294.     }
  1295.     else {
  1296.       where_to_stop = last + 1;
  1297.     }
  1298.   }
  1299.   
  1300.   for (target = ((last % 2) == 0) ? last + 2 : last + 1;
  1301.        target <= where_to_stop; target += 2) {
  1302.     
  1303.     low = target - 2;
  1304.     high = target - 1;
  1305.     twotothe = 1;
  1306.     splitter = target;
  1307.     
  1308.     while (((splitter % 2) == 0) && (low >= 0)) {
  1309.       if (low < realhigh) {
  1310.         if (! field_merge_index_file(low,(high > realhigh) ? realhigh : high,
  1311.                                      db,
  1312.                                      completely && (! low) &&
  1313.                                      !probe_file(field_index_filename(filename, db->fields[i].field_name,
  1314.                                                                       db)), i)) {
  1315.           panic("Error in merging file %ld into %ld", low,
  1316.                 (high > last) ? last : high);
  1317.         }
  1318.         realhigh = low;
  1319.       }
  1320.       high -= twotothe;
  1321.       twotothe *= 2;
  1322.       low -= twotothe;
  1323.       splitter /= 2;
  1324.     }
  1325.   }
  1326.   
  1327.   if (completely) {
  1328.     if (probe_file(field_index_filename(filename, db->fields[i].field_name, 
  1329.                                         db))) {
  1330.       field_merge_index_file(-2L, 0L, db, true, i);
  1331.     }
  1332.     else if (last == 0) {
  1333.       touch_file(field_index_filename_with_version(1, filename, i, db));
  1334.       field_merge_index_file(0L, 1, db, true, i);
  1335.       /* rename 0 into the final name */
  1336.       rename(field_index_filename_with_version(0L, filename2, i, db),
  1337.              field_index_filename(filename, db->fields[i].field_name, db));
  1338.     }
  1339.   }
  1340. }
  1341. #endif  /* FIELDS */
  1342. #endif  /* #ifndef END_MERGE */ 
  1343.  
  1344.     
  1345. static void merge_index_files _AP((database* db));
  1346.  
  1347. static void merge_index_files(db)
  1348. database *db;
  1349. /* this merges all the temporary inverted files into a large on 
  1350.  * and creates a dictionary.
  1351.  * This is done in a logrithmic merge of the files.
  1352.  * An n-ary merge would be faster of course, but what the heck... 
  1353.  */
  1354. {
  1355.   /* this version does it two at a time */
  1356.   char filename[MAX_FILENAME_LEN];
  1357.   char filename2[MAX_FILENAME_LEN];
  1358.   long level;
  1359.   long final_level;
  1360.   long number_of_files_to_merge = db->index_file_number;
  1361.   /* at level 0, merge 0,1->0; 2,3->2; 4,5->4; 6,7->6; 
  1362.      at level 1, merge 0,2->0; 4,6->4;
  1363.      at level 2, merge 0,4->0;
  1364.      stop.
  1365.      If there is only 1 file, then dont merge (final_level will be -1),
  1366.      the dictionary will be generated by merge after the for loop.
  1367.      */
  1368.   final_level = logcount(number_of_files_to_merge - 1) - 1;
  1369.   for(level = 0; level <= final_level; level++){
  1370.     long into_version;
  1371.     for(into_version = 0; 
  1372.     into_version < number_of_files_to_merge; 
  1373.     into_version += (2 << level)){
  1374.       long from_version = into_version + (1 << level);
  1375.       if(from_version < number_of_files_to_merge){
  1376.     boolean generate_dictionary; /* if this is the final level and 
  1377.                     there is no .inv file then yes */
  1378.     if(level == final_level){
  1379.       if(probe_file(index_filename(filename, db)))
  1380.         generate_dictionary = false;
  1381.       else generate_dictionary = true;
  1382.     }
  1383.     else{ 
  1384.       generate_dictionary = false;
  1385.     }
  1386.     
  1387.     /* printf("Level %d (out of %d) merging into %d from %d\n", 
  1388.            level, final_level, into_version, from_version); */
  1389.     if(false == merge_index_file(into_version, from_version, db,
  1390.                      generate_dictionary))
  1391.       panic("Error in merging file %ld into %ld", 
  1392.         from_version, into_version);
  1393.       }
  1394.     }
  1395.   } /* done merging */
  1396.   if(probe_file(index_filename(filename, db))){
  1397.     /* if there is a .inv file, then we are adding to an existing db. merge */ 
  1398.     /* do this by merging straight into the final using the special -2 version
  1399.        for the .inv file */
  1400.     merge_index_file(-2L, 0L, db, true);    
  1401.   }
  1402.   else if(number_of_files_to_merge == 1){
  1403.     /* then we have to generate a dictionary for this one file. 
  1404.        create a dummy file in 1, then merge that while making a dictionary
  1405.        */
  1406.     touch_file(index_filename_with_version(1, filename, db));
  1407.     merge_index_file(0L, 1, db, true);
  1408.     /* rename 0 into the final name */
  1409.     rename(index_filename_with_version(0L,filename2, db),
  1410.        index_filename(filename, db));
  1411.   }
  1412. }    
  1413.  
  1414. #ifdef FIELDS /* tung, 1/94 */
  1415. static void field_merge_index_files _AP((database* db, long field_id));
  1416.  
  1417. static void field_merge_index_files(db, field_id)
  1418.      database *db;
  1419.      long field_id;
  1420. /* this merges all the temporary inverted files into a large on 
  1421.  * and creates a dictionary.
  1422.  * This is done in a logrithmic merge of the files.
  1423.  * An n-ary merge would be faster of course, but what the heck... 
  1424.  */
  1425. {
  1426.   /* this version does it two at a time */
  1427.   char filename[MAX_FILENAME_LEN];
  1428.   char filename2[MAX_FILENAME_LEN];
  1429.   long level;
  1430.   long final_level;
  1431.   long number_of_files_to_merge; 
  1432.   long i = field_id;
  1433.  
  1434.   /* at level 0, merge 0,1->0; 2,3->2; 4,5->4; 6,7->6; 
  1435.      at level 1, merge 0,2->0; 4,6->4;
  1436.      at level 2, merge 0,4->0;
  1437.      stop.
  1438.      If there is only 1 file, then dont merge (final_level will be -1),
  1439.      the dictionary will be generated by merge after the for loop.
  1440.      */
  1441.  
  1442.   number_of_files_to_merge = db->fields[i].index_file_number;
  1443.   final_level = logcount(number_of_files_to_merge - 1) - 1;
  1444.   for(level = 0; level <= final_level; level++){
  1445.     long into_version;
  1446.     for(into_version = 0; 
  1447.         into_version < number_of_files_to_merge; 
  1448.         into_version += (2 << level)){
  1449.       long from_version = into_version + (1 << level);
  1450.       if(from_version < number_of_files_to_merge){
  1451.         boolean generate_dictionary; /* if this is the final level and 
  1452.                                         there is no .inv file then yes */
  1453.         if(level == final_level){
  1454.           if(probe_file(index_filename(filename, db)))
  1455.             generate_dictionary = false;
  1456.           else generate_dictionary = true;
  1457.         }
  1458.         else{ 
  1459.           generate_dictionary = false;
  1460.         }
  1461.         
  1462.         /* printf("Level %d (out of %d) merging into %d from %d\n", 
  1463.            level, final_level, into_version, from_version); */
  1464.         if(false == field_merge_index_file(into_version, from_version, db,
  1465.                                            generate_dictionary, i))
  1466.           panic("Error in merging file %ld into %ld", 
  1467.                 from_version, into_version);
  1468.       }
  1469.     }
  1470.   } /* done merging */
  1471.   if(probe_file(field_index_filename(filename,db->fields[i].field_name,db))){
  1472.     /* if there is a .inv file, then we are adding to an existing db. merge */ 
  1473.     /* do this by merging straight into the final using the special -2 version
  1474.        for the .inv file */
  1475.     field_merge_index_file(-2L, 0L, db, true, i);    
  1476.   }
  1477.   else if(number_of_files_to_merge == 1){
  1478.     /* then we have to generate a dictionary for this one file. 
  1479.        create a dummy file in 1, then merge that while making a dictionary
  1480.        */
  1481.     touch_file(field_index_filename_with_version(1, filename, i, db));
  1482.     field_merge_index_file(0L, 1, db, true, i);
  1483.     /* rename 0 into the final name */
  1484.     rename(field_index_filename_with_version(0L,filename2, i, db),
  1485.            field_index_filename(filename, db->fields[i].field_name, db));
  1486.   }
  1487. }
  1488. #endif  /* FIELDS */
  1489.  
  1490. /* ============================================ */
  1491. /* ===  Flushing the memory version of the  === */
  1492. /* ===  word hashtable to disk files        === */
  1493. /* ============================================ */
  1494.  
  1495. static void flush_word_entry_to_file _AP((hash_entry* wrd_entry,
  1496.                       database* db));
  1497.  
  1498. static void flush_word_entry_to_file(wrd_entry,db)
  1499. hash_entry* wrd_entry;
  1500. database* db;
  1501. {
  1502.   /* In this version, each word_entry is made into an index block.
  1503.      This means that there may be many small index blocks if the memory
  1504.      can not hold many files worth of data.  This approach was taken 
  1505.      for simplicities sake and it might be the best approach if there
  1506.      is a small vocabulary.
  1507.      
  1508.      This assumes that the index stream is positioned at the end.
  1509.      */
  1510.  
  1511.   if(wrd_entry->number_of_occurances > MAX_OCCURANCES){
  1512.     /* there are too many of this word, do nothing.
  1513.      * this may result in some having been written before, or 
  1514.      * no occurances of this word might be recorded.  Is this right?
  1515.      * if the first MAX_OCCURANCES want to be recored, then
  1516.      * add this clause to this condition:
  1517.      *     && wrd_entry->starting_block_number != 0
  1518.      */
  1519.     return;
  1520.   }
  1521.   if((wrd_entry->current_memory_ptr - wrd_entry->memory_ptr +
  1522.       INDEX_BLOCK_HEADER_SIZE) >=
  1523.      (1L << (8 * INDEX_BLOCK_SIZE_SIZE)))
  1524.     return; /* there are too many entries to store away, therefore throw it all away */
  1525.   
  1526.   if(NULL == wrd_entry->memory_ptr){
  1527.     /* not entries to write out */
  1528.     return;
  1529.   }
  1530.   if(0 == (wrd_entry->current_memory_ptr - wrd_entry->memory_ptr)){
  1531.     return;            /* there are no word entries to write */
  1532.   }
  1533.   /* printf("Flushing word %s\n", wrd_entry->key); */
  1534.   /* if this is the entry for this word, the put in the dictionary
  1535.      entry in the index file */
  1536.   write_dictionary_index_block(wrd_entry->number_of_occurances,
  1537.                    wrd_entry->key,
  1538.                    db->index_stream);
  1539.  
  1540.   db->number_of_words++;
  1541.  
  1542.   /* allocate and write the new block */
  1543.   allocate_index_block(wrd_entry->current_memory_ptr -
  1544.                wrd_entry->memory_ptr +
  1545.                INDEX_BLOCK_HEADER_SIZE,
  1546.                db->index_stream);
  1547.   /* cprintf(PRINT_AS_INDEXING, "New block number: %ld\n", new_block); */
  1548.   if((wrd_entry->current_memory_ptr - wrd_entry->memory_ptr) !=
  1549.      fwrite(wrd_entry->memory_ptr, 1L, (wrd_entry->current_memory_ptr -
  1550.                     wrd_entry->memory_ptr),
  1551.         db->index_stream))
  1552.     panic("Write failed");
  1553.   /* free the memory for the block written out */
  1554.   free_word_occurance_block(wrd_entry->memory_ptr);
  1555.   wrd_entry->memory_ptr =  NULL;
  1556.   wrd_entry->current_memory_ptr = NULL;
  1557.   wrd_entry->memory_size = 0;
  1558.   wrd_entry->current_doc_id = 0;
  1559. }
  1560.   
  1561. #ifdef FIELDS /* tung, 12/93 */
  1562. static void field_flush_word_entry_to_file _AP((hash_entry* wrd_entry,
  1563.                                                 long field_id, database* db));
  1564.  
  1565. static void field_flush_word_entry_to_file(wrd_entry, field_id, db)
  1566. hash_entry* wrd_entry;
  1567. long field_id;
  1568. database* db;
  1569. {
  1570.   /* In this version, each word_entry is made into an index block.
  1571.      This means that there may be many small index blocks if the memory
  1572.      can not hold many files worth of data.  This approach was taken 
  1573.      for simplicities sake and it might be the best approach if there
  1574.      is a small vocabulary.
  1575.      
  1576.      This assumes that the index stream is positioned at the end.
  1577.      */
  1578.  
  1579.   if(db->fields[field_id].numeric) { /* insert only number */
  1580.     if(!isdigit(wrd_entry->key[0]) &&
  1581.        !(wrd_entry->key[0]=='-') &&
  1582.        !(wrd_entry->key[0]=='.'))
  1583.       return;
  1584.   }
  1585.     
  1586.   if(wrd_entry->number_of_occurances > MAX_OCCURANCES){
  1587.     /* there are too many of this word, do nothing.
  1588.      * this may result in some having been written before, or 
  1589.      * no occurances of this word might be recorded.  Is this right?
  1590.      * if the first MAX_OCCURANCES want to be recored, then
  1591.      * add this clause to this condition:
  1592.      *     && wrd_entry->starting_block_number != 0
  1593.      */
  1594.     return;
  1595.   }
  1596.   if((wrd_entry->current_memory_ptr - wrd_entry->memory_ptr +
  1597.       INDEX_BLOCK_HEADER_SIZE) >=
  1598.      (1L << (8 * INDEX_BLOCK_SIZE_SIZE)))
  1599.     return; /* there are too many entries to store away, therefore throw it all away */
  1600.   
  1601.   if(NULL == wrd_entry->memory_ptr){
  1602.     /* not entries to write out */
  1603.     return;
  1604.   }
  1605.   if(0 == (wrd_entry->current_memory_ptr - wrd_entry->memory_ptr)){
  1606.     return;            /* there are no word entries to write */
  1607.   }
  1608.   /* printf("Flushing word %s\n", wrd_entry->key); */
  1609.   /* if this is the entry for this word, the put in the dictionary
  1610.      entry in the index file */
  1611.   write_dictionary_index_block(wrd_entry->number_of_occurances,
  1612.                    wrd_entry->key,
  1613.                    *(db->field_index_streams));
  1614.  
  1615.   db->fields[field_id].number_of_words++;
  1616.   
  1617.   /* allocate and write the new block */
  1618.   allocate_index_block(wrd_entry->current_memory_ptr -
  1619.                wrd_entry->memory_ptr +
  1620.                INDEX_BLOCK_HEADER_SIZE,
  1621.                *(db->field_index_streams));
  1622.   /* cprintf(PRINT_AS_INDEXING, "New block number: %ld\n", new_block); */
  1623.   if((wrd_entry->current_memory_ptr - wrd_entry->memory_ptr) !=
  1624.      fwrite(wrd_entry->memory_ptr, 1L, (wrd_entry->current_memory_ptr -
  1625.                     wrd_entry->memory_ptr),
  1626.         *(db->field_index_streams)))
  1627.     panic("Write failed");
  1628.   /* free the memory for the block written out */
  1629.   free_word_occurance_block(wrd_entry->memory_ptr);
  1630.   wrd_entry->memory_ptr =  NULL;
  1631.   wrd_entry->current_memory_ptr = NULL;
  1632.   wrd_entry->memory_size = 0;
  1633.   wrd_entry->current_doc_id = 0;
  1634. }
  1635. #endif  /* FIELDS */
  1636.  
  1637. /* This flushes all of the memory version of the word hashtable to disk.
  1638.  * this is called when the hashtable is filling up or we have 
  1639.  * accumulated enough words.  If completely is true, then it will merge 
  1640.  * all the intermediate files.
  1641.  */
  1642. void flush_memory_hashtable_to_disk(db,completely)
  1643.      database* db;
  1644.      boolean completely;
  1645. {
  1646.   /* map over the memory word hashtable and write the entries to disk */
  1647.   long i;
  1648.   char filename[1000];
  1649.     
  1650.   /* analyze the hash distribution 
  1651.      analyze_hashtable_distribution(db->the_word_memory_hashtable);
  1652.   */
  1653.   
  1654.   if(db->index_stream != NULL)
  1655.     s_fclose(db->index_stream);
  1656.  
  1657.   db->index_stream = 
  1658.     s_fopen(index_filename_with_version(db->index_file_number++, filename, db),
  1659.         "wb");
  1660.   if(NULL == db->index_stream)
  1661.     panic("Could not open file %s to insert index", 
  1662.       index_filename_with_version(db->index_file_number, filename, db));
  1663.   
  1664.   waislog(WLOG_MEDIUM, WLOG_INDEX,
  1665.       "Flushing %ld different words, %ld total words to disk...", 
  1666.       hashtable_count(db->the_word_memory_hashtable),
  1667.       db->number_of_words_in_hashtable);
  1668.  
  1669.   db->number_of_words = 0;
  1670.   write_bytes(0l, INDEX_HEADER_SIZE, db->index_stream);
  1671.  
  1672.   sort_hashtable(db->the_word_memory_hashtable);
  1673.   for(i = 0; i < hashtable_count(db->the_word_memory_hashtable); i++){
  1674.     flush_word_entry_to_file(&db->the_word_memory_hashtable->contents[i], db);
  1675.   }
  1676.  
  1677.   /* write out the number of entries into the index_file header */
  1678.   s_fseek(db->index_stream, 0L, SEEK_SET);
  1679.   write_bytes(db->number_of_words, INDEX_HEADER_SIZE, db->index_stream);
  1680.   s_fclose(db->index_stream);
  1681.   
  1682.   /* since everything is written out, we can flush these. */
  1683.   flush_word_occurance_buffers(); 
  1684.   clr_hashtable(db->the_word_memory_hashtable);
  1685.   db->number_of_words_in_hashtable = 0;
  1686.   
  1687.   /* add the stopwords to the index for the next session. */
  1688.   add_stop_words(db->the_word_memory_hashtable);
  1689.   
  1690.   waislog(WLOG_MEDIUM, WLOG_INDEX,
  1691.       "Done flushing version %ld", db->index_file_number - 1);
  1692.   /* scan_index_blocks(filename); */
  1693.  
  1694. #ifdef END_MERGE
  1695.   if(completely){
  1696.     waislog(WLOG_MEDIUM, WLOG_INDEX,
  1697.         "Merging %ld files",
  1698.         db->index_file_number);
  1699.     merge_index_files(db);
  1700.     waislog(WLOG_MEDIUM, WLOG_INDEX,
  1701.         "Done merging.");
  1702.   }
  1703. #else
  1704.   
  1705.   do_intermediate_merging(db, completely);
  1706.   
  1707. #endif   /* #ifdef END_MERGE */
  1708. }
  1709.  
  1710. #ifdef FIELDS /* tung, 12/93 */
  1711. void field_flush_memory_hashtable_to_disk(db, completely, field_id)
  1712.      database* db;
  1713.      boolean completely;
  1714.      long field_id;
  1715. {
  1716.   /* map over the memory word hashtable and write the entries to disk */
  1717.   long i = field_id;
  1718.   long j;
  1719.   char filename[1000];
  1720.     
  1721.   if(*(db->field_index_streams) != NULL)
  1722.     s_fclose(*(db->field_index_streams));
  1723.  
  1724.   *(db->field_index_streams) =
  1725.     s_fopen(field_index_filename_with_version(db->fields[i].index_file_number++, filename, i, db), "wb");
  1726.   if(NULL == *(db->field_index_streams))
  1727.     panic("Could not open file %s to insert index", 
  1728.           field_index_filename_with_version(db->fields[i].index_file_number, 
  1729.                                             filename, i, db));
  1730.   
  1731.   waislog(WLOG_MEDIUM, WLOG_INDEX,
  1732.           "Flushing %ld different words, %ld total words of field %s to disk...", 
  1733.           hashtable_count(db->the_word_memory_hashtable),
  1734.           db->fields[i].number_of_words_in_hashtable, 
  1735.           db->fields[i].field_name);
  1736.   db->fields[i].number_of_words = 0;
  1737.   write_bytes(0l, INDEX_HEADER_SIZE, *(db->field_index_streams));
  1738.   if(db->fields[i].numeric)
  1739.     sort_hashtable_numeric(db->the_word_memory_hashtable);
  1740.   else sort_hashtable(db->the_word_memory_hashtable);
  1741.   for(j = 0; j < hashtable_count(db->the_word_memory_hashtable); j++) {
  1742.     field_flush_word_entry_to_file(&db->the_word_memory_hashtable->contents[j], i, db);
  1743.   }
  1744.   s_fseek(*(db->field_index_streams), 0L, SEEK_SET);
  1745.   write_bytes(db->fields[i].number_of_words, INDEX_HEADER_SIZE, 
  1746.               *(db->field_index_streams));
  1747.   s_fclose(*(db->field_index_streams));
  1748.   /* since everything is written out, we can flush these. */
  1749.   
  1750.   flush_word_occurance_buffers(); 
  1751.   clr_hashtable(db->the_word_memory_hashtable);
  1752.   db->fields[i].number_of_words_in_hashtable = 0;
  1753.   /* add the stopwords to the index for the next session. */
  1754.   add_stop_words(db->the_word_memory_hashtable);
  1755.   
  1756. #ifdef END_MERGE
  1757.   if(completely){
  1758.     waislog(WLOG_MEDIUM, WLOG_INDEX,
  1759.         "Merging %ld files",
  1760.         db->index_file_number);
  1761.     field_merge_index_files(db, field_id);
  1762.     waislog(WLOG_MEDIUM, WLOG_INDEX,
  1763.         "Done merging.");
  1764.   }
  1765. #else
  1766.   
  1767.   field_do_intermediate_merging(db, completely, field_id);
  1768.   
  1769. #endif   /* #ifdef END_MERGE */
  1770. }
  1771. #endif  /* FIELDS */
  1772.  
  1773. long init_add_word(db, hashtable_size, flush_after_n_words)
  1774.      database *db;
  1775.      long hashtable_size;
  1776.      long flush_after_n_words;
  1777. {
  1778.   char fn[256];
  1779.   if(NULL != db->the_word_memory_hashtable)
  1780.     free_hashtable(db->the_word_memory_hashtable);
  1781.   db->the_word_memory_hashtable =
  1782.     make_hashtable(0, hashtable_size, sizeof(hash_entry));
  1783.   db->flush_after_n_words = flush_after_n_words;
  1784.   add_stop_words(db->the_word_memory_hashtable);
  1785.   strcpy( fn,db->database_file );
  1786.   strcat( fn,synonym_ext );
  1787.   syn_ReadFile( fn,&db->syn_Table,&db->syn_Table_Size );
  1788.   
  1789.   return(0);
  1790. }
  1791.  
  1792.  
  1793. #ifdef FIELDS /* tung, 12/93 */
  1794. long field_finished_add_word(db, field_id)
  1795.      database *db;
  1796.      long field_id;
  1797. {
  1798.   field_flush_memory_hashtable_to_disk(db, true, field_id);
  1799.   return(0); /* successful */
  1800. }
  1801. #endif  /* FIELDS */
  1802.  
  1803.  
  1804. long finished_add_word(db)
  1805. database *db;
  1806. {
  1807.   flush_memory_hashtable_to_disk(db,true);
  1808.   /* syn_Free( db->syn_Table,&db->syn_Table_Size ); */
  1809.   /* very bad idea for fields !!! */
  1810.   return(0); /* successful */
  1811. }
  1812.  
  1813.